
from data_types import Point, Line, Segment, DirectedSegment
import math

class TwoPointSide:
    SAME_SIDE = 0
    DIFFERENT_SIDES = 1
    ON_LINE = 2

def points_in_different_sides_of_line(line, point1, point2):
    v = line.put_point(point1) * line.put_point(point2)
    if v == 0:
        return TwoPointSide.ON_LINE
    elif v < 0:
        return TwoPointSide.DIFFERENT_SIDES
    else:
        return TwoPointSide.SAME_SIDE
    
def point_on_segment(segment, point):
    v = segment.put_point(point)
    on_line = v == 0
    in_x_range = min(segment.p1.x, segment.p2.x) <= point.x <= max(segment.p1.x, segment.p2.x)
    in_y_range = min(segment.p1.y, segment.p2.y) <= point.y <= max(segment.p2.y, segment.p2.y)
    return on_line and in_x_range and in_y_range

class SameDirection:
    Clockwise = 0
    CounterClockwise = 1
    SameLine = 2

def segment_axis(segment):
    return Segment(
        Point(segment.p1.x - segment.p2.x, segment.p1.y - segment.p2.y),
        Point(0, 0)
    )
    
def same_direction(directed_segment1, directed_segment2):
    segment_axis1 = segment_axis(directed_segment1)
    segment_axis2 = segment_axis(directed_segment2)
    
    x1 = segment_axis1.p1.x
    y1 = segment_axis1.p1.y
    
    x2 = segment_axis2.p1.x
    y2 = segment_axis2.p1.y

    v = x1 * y2 - x2 * y1

    if v > 0: 
        return SameDirection.CounterClockwise
    elif v == 0: 
        return SameDirection.SameLine
    elif v < 0: 
        return SameDirection.Clockwise

class TurnDirection:
    LEFT = 0
    RIGHT = 1
    SAME_LINE = 2

def turn_direction(directed_segment1, directed_segment2):
    is_same_direction = same_direction(
        directed_segment1, 
        Segment(directed_segment2.p2, directed_segment2.p1)
    )
    return {
        SameDirection.CounterClockwise : TurnDirection.RIGHT,
        SameDirection.SameLine : TurnDirection.SAME_LINE,
        SameDirection.Clockwise : TurnDirection.LEFT,
    }[is_same_direction]
    

def points_in_different_sides_of_segment(segment, point1, point2):
    point1_direction = same_direction(segment, Segment(segment.p1, point1))
    point2_direction = same_direction(segment, Segment(segment.p1, point2))
    is_same_line1 = point1_direction == SameDirection.SameLine
    is_same_line2 = point2_direction == SameDirection.SameLine
    if is_same_line1 or is_same_line2:
        return TwoPointSide.ON_LINE
    
    is_same_direction = point1_direction == point2_direction
    if is_same_direction:
        return TwoPointSide.SAME_SIDE
    else:
        return TwoPointSide.DIFFERENT_SIDES

def segments_intersect(segment1, segment2):
    segment1_crosses_segment2 = points_in_different_sides_of_segment(
        segment2, segment1.p1, segment1.p2) == TwoPointSide.DIFFERENT_SIDES
    segment2_crosses_segment1 = points_in_different_sides_of_segment(
        segment1, segment2.p1, segment2.p2) == TwoPointSide.DIFFERENT_SIDES

    return segment1_crosses_segment2 and segment2_crosses_segment1

def segment_length(segment):
    dx = segment.p1.x - segment.p2.x
    dy = segment.p1.y - segment.p2.y
    return math.sqrt(dx * dx + dy * dy) # we must use sqrt

def segment_point_distance(segment, point):
    return segment.put_point(point) / segment_length(segment)

def segment_angle(segment):
    length = segment_length(segment)

    return (segment.p1.x * segment.p2.x + segment.p1.y * segment.p2.y) / length

def segments_angle(segment1, segment2):
    segment_axis1 = segment_axis(segment1)
    segment_axis2 = segment_axis(segment2)

    return segment_angle(Segment(segment_axis1.p1, segment_axis2.p1))

def any_point_in_triangle(triangle):
    return Point(
        (triangle.p1.x + triangle.p2.x + triangle.p3.x) / 3,
        (triangle.p1.y + triangle.p2.y + triangle.p3.y) / 3
    )

def point_in_triangle_phase(triangle, point):
    segment1 = Segment(triangle.p1, triangle.p2)
    segment2 = Segment(triangle.p2, triangle.p3)
    segment3 = Segment(triangle.p3, triangle.p1)
    any_point = any_point_in_triangle(triangle)
    
    same_side1 = points_in_different_sides_of_segment(segment1,
        any_point, point) == TwoPointSide.SAME_SIDE
    same_side2 = points_in_different_sides_of_segment(segment2,
        any_point, point) == TwoPointSide.SAME_SIDE
    same_side3 = points_in_different_sides_of_segment(segment3,
        any_point, point) == TwoPointSide.SAME_SIDE

    return (same_side1, same_side2, same_side3)


def point_in_triangle(triangle, point):
    same_side1, same_side2, same_side3 = point_in_triangle_phase(
        triangle, point)
    return same_side1 and same_side2 and same_side3

def point_left_of_segment(point, segment):
    known_left_point = Point(segment.p1.x - 1, segment.p1.y)
    return points_in_different_sides_of_segment(segment, point, known_left_point)

class SegmentCompare:
    LEFT = 0
    RIGHT = 1
    SAME_LINE = 2
    
def segment_compare(segment1, segment2):
    s1p1 = segment1.p1
    s1p2 = segment1.p2
    s2p1 = segment2.p1
    s2p2 = segment2.p2
    
    min_s1 = min([s1p1, s1p2], key = lambda p: p.y)
    max_s1 = max([s1p1, s1p2], key = lambda p: p.y)
    min_s2 = min([s2p1, s2p2], key = lambda p: p.y)
    max_s2 = max([s2p1, s2p2], key = lambda p: p.y)
    
    if min_s2.y < s1p1.y < max_s2.y:
        same_point1 = s1p1
    else:
        same_point1 = s1p2
    
    if min_s1.y < s2p1.y < max_s1.y:
        same_point2 = s2p1
    else:
        same_point2 = s2p2
    
    if point_left_of_segment(same_point1, segment2):
        return SegmentCompare.LEFT
    elif point_left_of_segment(same_point2, segment1):
        return SegmentCompare.RIGHT
    else:
        return SegmentCompare.SAME_LINE